package top.milkbox.core;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;

/**
 * 有关字符串的操作
 * 自定义打印菜单
 * <p>
 * 创建时间：2023年2月14日
 * 最后修改：2023年2月14日
 *
 * @author guo
 */
public class UtilsString {

    /**
     * 判断字符是否是英文字符，在utf-8下，中文字符占3个字节
     *
     * @param ch 字符
     * @return true表示是英文字符
     */
    public static boolean isEnglishChar(char ch) {
        return (ch + "").getBytes().length == 1;
    }

    /**
     * 求字符串的等宽长度，在等宽字体下，一个英文字符站1格，一个中文字符占2格
     * 例如：getMonoFontWidth("123你好 1") 结果为9
     *
     * @param string 被求字符串
     * @return 总宽度
     */
    public static int getMonoFontWidth(String string) {
        int result = 0;
        for (int i = 0; i < string.length(); i++) {
            if (isEnglishChar(string.charAt(i))) {
                result += 1;
            } else {
                result += 2;
            }
        }
        return result;
    }

    /**
     * 连续打印n次字符串
     *
     * @param string 字符串
     * @param n      打印几次
     */
    public static void println(String string, int n) {
        System.out.print(getStringTimes(string, n));
        System.out.println();
    }

    /**
     * 重复生成同一个字符或字符串多次
     *
     * @param string 字符串
     * @param n      几次
     * @return 返回结果
     */
    public static String getStringTimes(String string, int n) {
        StringBuilder result = new StringBuilder();
        for (int i = 0; i < n; i++) {
            result.append(string);
        }
        return result.toString();
    }

    public static String getStringTimes(char ch, int n) {
        return getStringTimes("" + ch, n);
    }

    /**
     * 对齐方式
     */
    public enum AlignFace {
        LEFT, CENTER, RIGHT
    }

    /**
     * 在最大宽度下左对齐指定的字符串
     * 此方法的宽度是通过等宽字体的实际宽度计算，并非字符串的长度。
     * 所以说中英文混合状态下也可用
     * 一个英文占1格宽，一个中文占2格宽
     * <p>
     * 在居中对齐的情况下，由于整数类型的除法误差，左侧的填充字符串的宽度 <= 右侧的填充字符串的宽度
     * 当content的宽度大于等于maxWidth的时候不进行对齐操作
     * <p>
     * 举例如下
     * int maxWidth = 20;
     * char fill = '_'; // 为了方便查看，使用下划线替换空格
     * System.out.println(UtilsString.align("123 左对齐", maxWidth, UtilsString.AlignFace.LEFT, fill));
     * System.out.println(UtilsString.align("12 左对齐123", maxWidth, UtilsString.AlignFace.LEFT, fill));
     * System.out.println(UtilsString.align("居中对齐222", maxWidth, UtilsString.AlignFace.CENTER, fill));
     * System.out.println(UtilsString.align("123 居中对齐2 22", maxWidth, UtilsString.AlignFace.CENTER, fill));
     * System.out.println(UtilsString.align("adfa右对齐222", maxWidth, UtilsString.AlignFace.RIGHT, fill));
     * System.out.println(UtilsString.align("adfa过长不对齐 22222222222222222", maxWidth, UtilsString.AlignFace.RIGHT, fill));
     * <p>
     * 123 左对齐__________
     * 12 左对齐123________
     * ____居中对齐222_____
     * __123 居中对齐2 22__
     * _______adfa右对齐222
     * adfa过长不对齐 22222222222222222
     *
     * @param content  内容
     * @param maxWidth 最大字符宽度（不是.length长度）
     * @param face     对齐方向，枚举类型。传null不对齐
     * @param fill     对齐时填充的字符
     * @return 返回格式化的字符串
     */
    public static String align(String content, int maxWidth, AlignFace face, char fill) {
        // 内容字符串的宽度
        int contentWidth = getMonoFontWidth(content);
        // 当content的宽度大于等于maxWidth的时候不进行对齐操作。可有可无，提高性能
        if (contentWidth >= maxWidth) {
            return content;
        }
        // 需要填充的字符的总宽度
        int fillStringWidth = maxWidth - contentWidth;
        // 拼接出所有的填充字符
        String fillString = getStringTimes(fill, fillStringWidth);
        // 判断对齐方向
        if (face == AlignFace.LEFT) {
            return content + fillString;
        } else if (face == AlignFace.RIGHT) {
            return fillString + content;
        } else if (face == AlignFace.CENTER) {
            int fillStringWidthPart = fillStringWidth / 2; // 填充字符串宽度的一般
            return getStringTimes(fill, fillStringWidthPart)
                    + content
                    + getStringTimes(fill, fillStringWidth - fillStringWidthPart);
        } else {
            return content;
        }
    }

    public static String alignLeft(String content, int maxWidth) {
        return align(content, maxWidth, AlignFace.LEFT, ' ');
    }

    public static String alignRight(String content, int maxWidth) {
        return align(content, maxWidth, AlignFace.RIGHT, ' ');
    }

    public static String alignCenter(String content, int maxWidth) {
        return align(content, maxWidth, AlignFace.CENTER, ' ');
    }

    /**
     * 使用map的方式打印一个菜单，个性化设置较少，下面有更好的方法
     * 举例：
     * LinkedHashMap<String, String> map = new LinkedHashMap<>();
     * map.put("1", "选择文件/重新选择文件");
     * map.put("2", "转码");
     * map.put("3", "退出");
     * menuPrintStyleDefault(map);
     * ###############################
     * # 1 >>> 选择文件/重新选择文件 #
     * # 2 >>> 转码                  #
     * # 3 >>> 退出                  #
     * ###############################
     *
     * @param menuMap key和value的map
     */
    public static void menuPrintStyleDefault(LinkedHashMap<String, String> menuMap) {
        // 每一行选项的前缀、后缀和分隔符
        String prefix = "# ";
        String suffix = " #";
        String separator = " >>> ";
        int prefixWidth = getMonoFontWidth(prefix);
        int suffixWidth = getMonoFontWidth(suffix);
        // 分别寻找键和值字符串长度最长的长度
        int maxKeyWidth = 0;
        int maxValueWidth = 0;
        for (String key : menuMap.keySet()) {
            maxKeyWidth = Math.max(getMonoFontWidth(key), maxKeyWidth);
            maxValueWidth = Math.max(getMonoFontWidth(menuMap.get(key)), maxValueWidth);
        }
        // 最大宽度
        int itemOptionMaxWidth = getMonoFontWidth(prefix + suffix + separator)
                + maxKeyWidth + maxValueWidth;
        println("#", itemOptionMaxWidth);
        menuMap.forEach((key, value) -> System.out.println(
                prefix + alignLeft(key + separator + value,
                        itemOptionMaxWidth - prefixWidth - suffixWidth) + suffix));
        println("#", itemOptionMaxWidth);
    }


    /**
     * 打印个性化菜单的类
     */
    public static class Menu {
        private String borderLeft = "# "; // 每一行的左边框
        private String borderRight = " #"; // 每一行的右边框
        private char borderTopChar = '#'; // 上边框单个字符
        private char borderBottomChar = '#'; // 下边框单个字符

        private int borderLeftWidth = getMonoFontWidth(borderLeft); // 每一行的左边框宽度
        private int borderRightWidth = getMonoFontWidth(borderRight); // 每一行的右边框宽度

        private String separator = " >>> "; // 分割
        private int separatorWidth = getMonoFontWidth(separator); // 中间分割字符串的宽度

        private char fillChar = ' '; // 对齐时的填充字符

        private List<Option> optionList; // 每一行的内容

        /**
         * 根据当前对象的各个参数打印菜单
         * 后面有main方法举例
         */
        public void printMenu() {
            // 分别寻找键和值字符串长度最长的长度
            int maxKeyWidth = 0;
            int maxValueWidth = 0;
            for (Option option : optionList) {
                maxKeyWidth = Math.max(getMonoFontWidth(option.getKey()), maxKeyWidth);
                maxValueWidth = Math.max(getMonoFontWidth(option.getValue()), maxValueWidth);
            }
            // 最大宽度
            int itemOptionMaxWidth = borderLeftWidth + borderRightWidth + separatorWidth + maxKeyWidth + maxValueWidth;
            // 打印上边框
            System.out.println(getStringTimes(borderTopChar, itemOptionMaxWidth));
            optionList.forEach(option ->
                    System.out.println(borderLeft + align(
                            option.getKey() + separator + option.getValue(),
                            itemOptionMaxWidth - borderLeftWidth - borderRightWidth,
                            option.getAlignFace(),
                            fillChar
                    ) + borderRight)
            );
            // 打印下边框
            System.out.println(getStringTimes(borderBottomChar, itemOptionMaxWidth));
        }

        public Menu(List<Option> optionList) {
            this.optionList = optionList;
        }

        public Menu(String borderLeft, String borderRight, char borderTopChar, char borderBottomChar,
                    String separator, char fillChar, List<Option> optionList) {
            this.borderLeft = borderLeft;
            this.borderRight = borderRight;
            this.borderTopChar = borderTopChar;
            this.borderBottomChar = borderBottomChar;
            this.borderLeftWidth = getMonoFontWidth(borderLeft);
            this.borderRightWidth = getMonoFontWidth(borderRight);
            this.separator = separator;
            this.separatorWidth = getMonoFontWidth(separator);
            this.fillChar = fillChar;
            this.optionList = optionList;
        }

        public char getFillChar() {
            return fillChar;
        }

        public void setFillChar(char fillChar) {
            this.fillChar = fillChar;
        }

        public String getBorderLeft() {
            return borderLeft;
        }

        public void setBorderLeft(String borderLeft) {
            this.borderLeft = borderLeft;
            this.borderLeftWidth = getMonoFontWidth(borderLeft);
        }

        public String getBorderRight() {
            return borderRight;
        }

        public void setBorderRight(String borderRight) {
            this.borderRight = borderRight;
            this.borderRightWidth = getMonoFontWidth(borderRight);
        }

        public char getBorderTopChar() {
            return borderTopChar;
        }

        public void setBorderTopChar(char borderTopChar) {
            this.borderTopChar = borderTopChar;
        }

        public char getBorderBottomChar() {
            return borderBottomChar;
        }

        public void setBorderBottomChar(char borderBottomChar) {
            this.borderBottomChar = borderBottomChar;
        }

        public int getBorderLeftWidth() {
            return borderLeftWidth;
        }

        public int getBorderRightWidth() {
            return borderRightWidth;
        }

        public String getSeparator() {
            return separator;
        }

        public void setSeparator(String separator) {
            this.separator = separator;
            this.separatorWidth = getMonoFontWidth(separator);
        }

        public int getSeparatorWidth() {
            return separatorWidth;
        }

        public List<Option> getOptionList() {
            return optionList;
        }

        public void setOptionList(List<Option> optionList) {
            this.optionList = optionList;
        }

        @Override
        public String toString() {
            return "Menu{" +
                    "borderLeft='" + borderLeft + '\'' +
                    ", borderRight='" + borderRight + '\'' +
                    ", borderTopChar=" + borderTopChar +
                    ", borderBottomChar=" + borderBottomChar +
                    ", borderLeftWidth=" + borderLeftWidth +
                    ", borderRightWidth=" + borderRightWidth +
                    ", separator='" + separator + '\'' +
                    ", separatorWidth=" + separatorWidth +
                    ", fillChar=" + fillChar +
                    ", optionList=" + optionList +
                    '}';
        }

        public static class Option {
            private String key;
            private String value;
            private AlignFace alignFace = AlignFace.LEFT; // 默认左对齐

            public Option() {
            }

            public Option(String key, String value) {
                this.key = key;
                this.value = value;
            }

            public Option(String key, String value, AlignFace alignFace) {
                this.key = key;
                this.value = value;
                this.alignFace = alignFace;
            }

            @Override
            public String toString() {
                return "Item{" +
                        "key='" + key + '\'' +
                        ", value='" + value + '\'' +
                        ", alignFace=" + alignFace +
                        '}';
            }

            public String getKey() {
                return key;
            }

            public void setKey(String key) {
                this.key = key;
            }

            public String getValue() {
                return value;
            }

            public void setValue(String value) {
                this.value = value;
            }

            public AlignFace getAlignFace() {
                return alignFace;
            }

            public void setAlignFace(AlignFace alignFace) {
                this.alignFace = alignFace;
            }
        }

        public static void main(String[] args) {

            ArrayList<Option> options = new ArrayList<>();
            options.add(new Option("1", "选择文件/重新选择文件"));
            options.add(new Option("2", "转码"));
            options.add(new Option("3", "退出"));
            options.add(new Option("4", "左对齐asdfasdf"));
            options.add(new Option("5", "左对齐qerqewrqer"));
            options.add(new Option("6", "左对齐zxcvzxcv"));
            options.add(new Option("7", "居中ghjkghjkgjk", AlignFace.CENTER));
            options.add(new Option("8", "居中ghjkghjkgjk", AlignFace.CENTER));
            options.add(new Option("9", "居中ghjkghjkgjk", AlignFace.CENTER));
            options.add(new Option("10", "右对齐ghjkghjkgjk", AlignFace.RIGHT));
            options.add(new Option("11", "右对齐ghjkghjkgjk", AlignFace.RIGHT));
            options.add(new Option("12", "右对齐ghjkghjkgjk", AlignFace.RIGHT));

            // 默认风格打印
            Menu menu = new Menu(options);
            menu.printMenu();

            // 在不创建新对象的条件下打印自定义风格
            menu.setSeparator(" <----> ");
            menu.setBorderLeft("%% ");
            menu.setBorderRight(" %%");
            menu.setBorderTopChar('%');
            menu.setBorderBottomChar('%');
            menu.printMenu();

            ArrayList<Option> options2 = new ArrayList<>();
            options2.add(new Option("1", "选择文件/重新选择文件"));
            options2.add(new Option("2", "转码"));
            options2.add(new Option("3", "退出"));
            options2.add(new Option("4", "左对齐asdfasdf"));
            options2.add(new Option("5", "右对齐qerqewrqer", AlignFace.RIGHT));
            options2.add(new Option("6", "居中对齐zxcvzxcv", AlignFace.CENTER));

            // 使用个性化构造方法
            Menu menuStyle2 = new Menu(
                    "** ", " **",
                    '_', '^',
                    " ^_^ ", '.', options2);
            menuStyle2.printMenu();

            /*
                ################################
                # 1 >>> 选择文件/重新选择文件  #
                # 2 >>> 转码                   #
                # 3 >>> 退出                   #
                # 4 >>> 左对齐asdfasdf         #
                # 5 >>> 左对齐qerqewrqer       #
                # 6 >>> 左对齐zxcvzxcv         #
                #    7 >>> 居中ghjkghjkgjk     #
                #    8 >>> 居中ghjkghjkgjk     #
                #    9 >>> 居中ghjkghjkgjk     #
                #     10 >>> 右对齐ghjkghjkgjk #
                #     11 >>> 右对齐ghjkghjkgjk #
                #     12 >>> 右对齐ghjkghjkgjk #
                ################################
                %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
                %% 1 <----> 选择文件/重新选择文件  %%
                %% 2 <----> 转码                   %%
                %% 3 <----> 退出                   %%
                %% 4 <----> 左对齐asdfasdf         %%
                %% 5 <----> 左对齐qerqewrqer       %%
                %% 6 <----> 左对齐zxcvzxcv         %%
                %%    7 <----> 居中ghjkghjkgjk     %%
                %%    8 <----> 居中ghjkghjkgjk     %%
                %%    9 <----> 居中ghjkghjkgjk     %%
                %%     10 <----> 右对齐ghjkghjkgjk %%
                %%     11 <----> 右对齐ghjkghjkgjk %%
                %%     12 <----> 右对齐ghjkghjkgjk %%
                %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
                _________________________________
                ** 1 ^_^ 选择文件/重新选择文件 **
                ** 2 ^_^ 转码................. **
                ** 3 ^_^ 退出................. **
                ** 4 ^_^ 左对齐asdfasdf....... **
                ** .....5 ^_^ 右对齐qerqewrqer **
                ** ..6 ^_^ 居中对齐zxcvzxcv... **
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
             */
        }
    }


}
